package minijvm.loader;

import minijvm.clz.AccessFlag;
import minijvm.clz.ClassFile;
import minijvm.clz.ClassIndex;
import minijvm.constant.*;
import minijvm.field.Field;
import minijvm.method.Method;

/**
 * @author ChangLiang
 * @date 2020/4/12
 */
public class ClassFileParser {

    public ClassFile parse(byte[] codes) {
        ByteCodeIterator iter = new ByteCodeIterator(codes);
        String magicNumber = iter.nextU4ToHexString();
        if(!magicNumber.equals("cafebabe")){
            return null;
        }
        ClassFile classFile = new ClassFile();
        classFile.setMinorVersion(iter.nextU2ToInt());
        classFile.setMajorVersion(iter.nextU2ToInt());
        ConstantPool pool = parseConstantPool(iter);
        classFile.setPool(pool);
        classFile.setAccessFlag(parseAccessFlag(iter));
        classFile.setClzIndex(parseClassIndex(iter));
        parseInterfaces(iter);
        parseFields(classFile,iter);
        parseMethods(classFile,iter);
        return classFile;
    }

    private void parseMethods(ClassFile classFile, ByteCodeIterator iter) {
        int size = iter.nextU2ToInt();
        for (int i = 0; i < size; i++) {
            classFile.addMethod(Method.parse(classFile,iter));
        }
    }

    private void parseFields(ClassFile classFile, ByteCodeIterator iter) {
        int size = iter.nextU2ToInt();
        for (int i = 0; i < size; i++) {
            classFile.addField(Field.parse(classFile.getPool(),iter));
        }
    }

    private void parseInterfaces(ByteCodeIterator iter) {
        int interfaceCount = iter.nextU2ToInt();
        System.out.println("interfaceCount:" + interfaceCount);
        // TODO : 如果实现了interface, 这里需要解析
    }

    private AccessFlag parseAccessFlag(ByteCodeIterator iter) {
        AccessFlag flag = new AccessFlag(iter.nextU2ToInt());
        return flag;
    }

    private ClassIndex parseClassIndex(ByteCodeIterator iter) {
        ClassIndex classIndex = new ClassIndex();
        classIndex.setThisClassIndex(iter.nextU2ToInt());
        classIndex.setSuperClassIndex(iter.nextU2ToInt());
        return classIndex;

    }

    private ConstantPool parseConstantPool(ByteCodeIterator iter) {
        ConstantPool pool = new ConstantPool();
        int size = iter.nextU2ToInt();
        pool.addConstantInfo(new NullConstantInfo());
        for (int i = 0; i < size-1; i++) {
            int tag = iter.nextU1toInt();
            switch (tag){
                case ConstantInfo.UTF8_INFO:{
                    UTF8Info info = new UTF8Info(pool);
                    info.setLength(iter.nextU2ToInt());
                    info.setValue(iter.nextUxToString(info.getLength()));
                    pool.addConstantInfo(info);
                    break;
                }
                case ConstantInfo.FLOAT_INFO:{
                    break;
                }
                case ConstantInfo.CLASS_INFO:{
                    ClassInfo info = new ClassInfo(pool);
                    info.setUtf8Index(iter.nextU2ToInt());
                    pool.addConstantInfo(info);
                    break;
                }
                case ConstantInfo.STRING_INFO:{
                    StringInfo info = new StringInfo(pool);
                    info.setIndex(iter.nextU2ToInt());
                    pool.addConstantInfo(info);
                    break;
                }
                case ConstantInfo.FIELD_INFO:{
                    FieldRefInfo info = new FieldRefInfo(pool);
                    info.setClassInfoIndex(iter.nextU2ToInt());
                    info.setNameAndTypeIndex(iter.nextU2ToInt());
                    pool.addConstantInfo(info);
                    break;
                }
                case ConstantInfo.METHOD_INFO:{
                    MethodRefInfo info = new MethodRefInfo(pool);
                    info.setClassInfoIndex(iter.nextU2ToInt());
                    info.setNameAndTypeIndex(iter.nextU2ToInt());
                    pool.addConstantInfo(info);
                    break;
                }
                case ConstantInfo.NAME_AND_TYPE_INFO:{
                    NameAndTypeInfo info = new NameAndTypeInfo(pool);
                    info.setIndex1(iter.nextU2ToInt());
                    info.setIndex2(iter.nextU2ToInt());
                    pool.addConstantInfo(info);
                    break;
                }
                default:{
                    throw new RuntimeException("Unsupport tag:"+tag+"; It may support later");
                }
            }
        }
        return pool;
    }
}
